﻿namespace FSpec

open System
open System.Linq
open System.Collections.Generic 

[<AutoOpen>]
module Assertions =
    
    type BoundType =
    | Inclusive
    | Exclusive

    type GrammaticConjunction =
    | And
    | Of

    ///<summary>Verifies a value is true.</summary>
    let isTrue actual = actual = true
    
    ///<summary>Verifies a value is false.</summary>
    let isFalse actual = actual = false
    
    ///<summary>Verifies that the lhs is greater than the rhs.</summary>    
    let isGreaterThan rhs lhs = lhs > rhs

    ///<summary>Verifies that the lhs is less than the rhs.</summary>    
    let isLessThan rhs lhs = lhs < rhs
    
    ///<summary>Verifies that the lhs is less than or equal to the rhs.</summary>    
    let isLessThanOrEqualTo rhs lhs = lhs <= rhs
    
    ///<summary>Verifies that the lhs is greater than or equal to the rhs.</summary>    
    let isGreaterThanOrEqualTo rhs lhs = lhs >= rhs

    ///<summary>Verifies that both arguments are equal.</summary>    
    let equals rhs lhs = (lhs = rhs)

    ///<summary>Verifies that both arguments are referentially equal.</summary>    
    let isTheSameAs expected actual = Object.ReferenceEquals(actual, expected)

    ///<summary>Verifies that the collection contains the given element.</summary>    
    let contains (element:'a) (collection:#IEnumerable<'a>) = collection.Contains element
    
    ///<summary>Verifies that the source string contains the expected substring.</summary>    
    let containsTheString (expectedSubstring:string) (sourceString:string) = sourceString.Contains expectedSubstring
    
    ///<summary>Verifies that the source string starts with the expected substring.</summary>    
    let startsWith (expectedSubstring:string) (sourceString:string) = sourceString.StartsWith expectedSubstring 
    
    ///<summary>Verifies that the source string ends with the expected substring.</summary>    
    let endsWith (expectedSubstring:string) (sourceString:string) = sourceString.EndsWith expectedSubstring

    ///<summary>Verifies that the source string matches the given regular expression.</summary>    
    let matchExpression regularExpression options stringInput = 
            let rgx = new System.Text.RegularExpressions.Regex(regularExpression, options)
            rgx.IsMatch(stringInput)

    ///<summary>Verifies that the object reference is null (nothing in VB).</summary>
    let isNull actual =
        actual = null

    ///<summary>Verifies that the collection is empty.</summary>
    let isEmpty (collection:IEnumerable<'t>) =
        collection.Count() = 0

    ///<summary>Verifies that the instance of the given type.</summary>
    let isA (expectedType:Type) (instance:'a) =
        instance.GetType() = expectedType 

    ///<summary>Verifies that the block of code raises an exception of a given type.</summary>
    let throws (exceptionType:Type) (expression:unit->'a) =
        try
            expression() |> ignore
            false
        with
        | ex when ex.GetType() = exceptionType -> true
        | _ -> false

    ///<summary>Verifies that the block of code fails with a FailureException with a given message.</summary>
    let failsWith (message:string) (expression:unit->'a) =
        try
            expression() |> ignore
            false
        with
        | Failure(msg) -> msg = message

    ///<summary>Verifies that a value is between a range of values.
    let isBetween lowerBound (word:GrammaticConjunction) upperBound boundType value =
        match boundType with
        | Inclusive -> value >= lowerBound && value <= upperBound
        | Exclusive -> value > lowerBound && value < upperBound

    ///<summary>Verifies the value is within a given tolerance of the expected value.</summary>
    let isWithin (tolerance:float) conjunction expectedValue value =
        let lower = expectedValue - tolerance
        let upper = expectedValue + tolerance

        value |> isBetween lower And upper Inclusive
    
    ///<summary>Verifies a list intersects another list</summary>
    let intersects (list1:'a list) (list2:'a list) =
        let intersections = list1.Intersect list2
        intersections.Count() > 0
    
